package com.fm.magic.net.chrome;

import com.alibaba.fastjson.JSON;
import com.fm.base.log.Loggers;
import com.fm.commons.net.http.HttpClient;
import com.fm.commons.net.http.HttpRequest;
import com.fm.commons.net.http.HttpResponse;
import com.fm.commons.net.http.converter.StringConverter;
import com.neovisionaries.ws.client.WebSocket;
import com.neovisionaries.ws.client.WebSocketFactory;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Scanner;

/**
 * @author leo.li
 */
public class ChromeAgent implements AutoCloseable {
    private static String findChrome() {
        String os = System.getProperty("os.name").toLowerCase(Locale.ENGLISH);
        boolean windows = os.startsWith("windows");
        boolean osx = os.startsWith("mac");

        String chromePath = null;
        chromePath = getCustomChromeBinary();
        if (chromePath == null && windows) {
            chromePath = findChromeWinPath();
        }
        if (chromePath == null && osx) {
            chromePath = findChromeOsxPath();
        }
        if (chromePath == null && !windows) {
            chromePath = "google-chrome";
        }
        return chromePath;
    }

    private static String getCustomChromeBinary() {
        String chromeBinary = System.getProperty("chrome_binary");
        if (chromeBinary != null) {
            File chromeExecutable = new File(chromeBinary);
            if (chromeExecutable.exists() && chromeExecutable.canExecute()) {
                return chromeExecutable.getAbsolutePath();
            }
        }
        return null;
    }

    private static String findChromeWinPath() {
        try {
            for (String path : getChromeWinPaths()) {
                final Process process = Runtime.getRuntime().exec(new String[]{
                    "cmd", "/c", "echo", path
                });
                final int exitCode = process.waitFor();
                if (exitCode == 0) {
                    final String location = toString(process.getInputStream()).trim().replace("\"", "");
                    final File chrome = new File(location);
                    if (chrome.exists() && chrome.canExecute()) {
                        return chrome.toString();
                    }
                }
            }
            throw new RuntimeException("Unable to find chrome.exe");
        } catch (Throwable e) {
            // ignore
        }
        return null;
    }

    private static List<String> getChromeWinPaths() {
        return Arrays.asList(
            "%localappdata%\\Google\\Chrome SxS\\Application\\chrome.exe", // Chrome Canary
            "%programfiles%\\Google\\Chrome\\Application\\chrome.exe",     // Chrome Stable 64-bit
            "%programfiles(x86)%\\Google\\Chrome\\Application\\chrome.exe" // Chrome Stable 32-bit
        );
    }

    private static String findChromeOsxPath() {
        for (String path : getChromeOsxPaths()) {
            final File chrome = new File(path);
            if (chrome.exists() && chrome.canExecute()) {
                return chrome.toString();
            }
        }
        return null;
    }

    private static List<String> getChromeOsxPaths() {
        return Arrays.asList(
            "/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary", // Chrome Canary
            "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"                // Chrome Stable
        );
    }

    private static String toString(InputStream is) {
        try (Scanner scanner = new Scanner(is)) {
            scanner.useDelimiter("\\A");
            return scanner.hasNext() ? scanner.next() : "";
        }
    }

    private WebSocketFactory webSocketFactory;
    private List<SocketInfo> socketInfos;
    private String chromePath;
    private int port;
    private List<String> arguments;
    private HttpClient httpClient;

    public ChromeAgent() {
        this(findChrome());
    }

    public ChromeAgent(String chromePath) {
        this(chromePath, Collections.emptyList());
    }

    public ChromeAgent(String chromePath, List<String> arguments) {
        this(chromePath, 9222, arguments);
    }

    public ChromeAgent(String chromePath, int port) {
        this(chromePath, port, Collections.emptyList());
    }

    public ChromeAgent(String chromePath, int port, List<String> arguments) {
        this.chromePath = chromePath;
        this.port = port;
        this.httpClient = new HttpClient();
        this.arguments = arguments;
        this.webSocketFactory = new WebSocketFactory();
    }

    public void setConnectionTimeout(int connectionTimeout) {
        webSocketFactory.setConnectionTimeout(connectionTimeout);
    }

    public ChromeTab launch() throws IOException {
        if (PortUtil.isListen("localhost", 9222)) {
            throw new RuntimeException("port 9222 is used");
        }

        if (chromePath == null || chromePath.trim().isEmpty()) {
            throw new RuntimeException("chrome not found");
        }
        Path remoteProfileData = Paths.get(System.getProperty("java.io.tmpdir")).resolve("remote-profile");
        List<String> list = new ArrayList<>();
        list.add(chromePath);
        list.add(String.format("--remote-debugging-port=%d", port));
        list.add(String.format("--user-data-dir=%s", remoteProfileData.toString()));
        //if (!DEFAULT_HOST.equals(factory.getHost())) {
        //    list.add(format("--remote-debugging-address=%s", factory.getHost()));
        //}
        list.add("--disable-translate");
        list.add("--disable-extensions");
        list.add("--no-default-browser-check");
        list.add("--disable-plugin-power-saver");
        list.add("--disable-sync");
        list.add("--no-first-run");
        list.add("--safebrowsing-disable-auto-update");
        list.add("--disable-popup-blocking");

        if (!arguments.isEmpty()) {
            list.addAll(arguments);
        }

        try {
            Process process = Runtime.getRuntime().exec(list.toArray(new String[0]));
            process.getOutputStream().close();
            process.getInputStream().close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        try {
            for (int i = 0; i < 10; i++) {
                HttpRequest request = HttpRequest.get("http://localhost:" + port + "/json/list").build();
                HttpResponse response = httpClient.send(request);
                if (response.getStatusCode() == 200) {
                    socketInfos = JSON.parseArray(response.convert(StringConverter.Instance), SocketInfo.class);
                    if (socketInfos.size() == 1) {
                        SocketInfo info = socketInfos.get(0);
                        return new ChromeTab(webSocketFactory, info);
                    } else {
                        throw new RuntimeException("");
                    }
                }
                Thread.sleep(500);
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        throw new RuntimeException("启动失败");
    }

    @Override
    public void close() {
        if (socketInfos != null) {
            Iterator<SocketInfo> iterator = socketInfos.iterator();
            while (iterator.hasNext()) {
                SocketInfo info = iterator.next();
                HttpRequest request = HttpRequest.get("http://localhost:" + port + "/json/close/" + info.getId()).build();
                HttpResponse response = httpClient.send(request);
                if (response.getStatusCode() !=200) {
                    Loggers.CONSOLE.error("fail close " + info.getId());
                }
                iterator.remove();
            }
        }
        httpClient.close();
    }
}